﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace GameLibrary
{
    using Tile;
    using Unit;
    using Settlement;
    using Player;
    using Map;
    using Events;

    [Serializable]
    public class GameManager
    {
        /*
        #region singleton
        private class GameManagerGetter
        {
            GameManagerGetter() { }
            public static GameManager UniqueInstance = new GameManager();
        }
        public static GameManager UniqueInstance
        {
            get
            {
                return GameManagerGetter.UniqueInstance;
            }
        }
        #endregion
        */

        #region variables

        #region random generator

        public int RandomSeed {get;private set;}

        #endregion

        #region turn
        public static uint TurnNumber { get; private set; }
        public uint TotalTurns {get; private set;}
        private void IncreaseTurnNumber()
        {
            TurnNumber++;
        }
        #endregion

        #region constructor
        public GameManager()
        {
            TurnNumber = 0;
        }
        #endregion

        #region players
        public HumPlayer HumPlayer { get; private set; }
        /// <summary>
        /// Store players
        /// </summary>
        private List<Player.Player> PlayerList;
        #endregion

        #region relationship between all players
        /// <summary>
        /// Store player diplomatic relationships
        /// uint is player's id
        /// diplomatic relationship store relationship between player with first uint and player with id stored in diplomatic relationship
        /// </summary>
        public Dictionary<uint, Dictionary<uint,DiplomaticRelationshipType>> relationshipList;
        #endregion

        #endregion

        #region init game
        public void InitGame(
            int width = 5, 
            int height  = 4,
            bool haveHumanPlayer = true, 
            int humanInitialCityNumber = 1,
            int humanInitialUnitNumber = 3,
            int computerInitCityNumber = 1,
            int computerInitUnitNumber = 1,
            int comPlayerNumber = 1, 
            int totalTurn = 1000, 
            int randomSeed = 0
            )
        {
            this.RandomSeed = randomSeed;
            Random Rand = new Random();//new Random(randomSeed);
            GMHelper.SetRand(Rand);
            GMHelper.SetHeight(height);
            GMHelper.SetWidth(width);
            PlayerList = new List<Player.Player>(comPlayerNumber);
            if(haveHumanPlayer)
             this.HumPlayer = new HumPlayer(
                 this,
                  new Position(0,0),
                 //this.GetStartPosition(),
                 GMHelper.GetBias(),
                 humanInitialUnitNumber, 
                 humanInitialCityNumber
                 );

            for(int i=0;i<1;i++)
            {                
                PlayerList.Add(
                    new Player.Player(
                        this,
                         new Position(4,3),
                       // this.GetStartPosition(),
                        GMHelper.GetBias(),
                        computerInitUnitNumber, 
                        computerInitCityNumber
                        )
                        );
            }

            #region init relationship list
            this.relationshipList = new Dictionary<uint, Dictionary<uint,DiplomaticRelationshipType>>();

            this.relationshipList.Add(HumPlayer.ID, new Dictionary<uint, DiplomaticRelationshipType>());
            this.ConnectEvents(this.HumPlayer);

            foreach (var p in this.PlayerList)
            {
                this.relationshipList.Add(p.ID, new Dictionary<uint, DiplomaticRelationshipType>());
                this.ConnectEvents(p);
                p.SetRelationShip(this.relationshipList[p.ID]);
            }
            if (haveHumanPlayer)
                this.HumPlayer.SetRelationShip(this.relationshipList[this.HumPlayer.ID]);

            if (haveHumanPlayer)
            {
                this.HumPlayer.NextTurn();
            }
            #endregion
        }

        private Position GetStartPosition()
        {
            int x = GMHelper.Rand.Next(GMHelper.Width);
            int y = GMHelper.Rand.Next(GMHelper.Height);
            return new Position(x, y);
        }

        #endregion

        #region events
        private void ConnectEvents(Player.Player player)
        {
            player.MapObjectKilled += this.MapObjectKilled;
            player.NewRelationship += this.NewRelationship;
            player.DeclearWar += this.DeclearWar;
            player.PlayerDied += this.PlayerDied;
        }
        private void DisconnectEvents(Player.Player player)
        {
            player.MapObjectKilled -= this.MapObjectKilled;
            player.NewRelationship -= this.NewRelationship;
            player.PlayerDied -= this.PlayerDied;
            player.DeclearWar -= this.DeclearWar;
        }
        private void PlayerDied(object sender, PlayerDiedEventArgs e)
        {
            foreach (uint id in this.relationshipList[e.DiedPlayerID].Keys)
            {
                this.relationshipList[id].Remove(e.DiedPlayerID);
            }
            this.relationshipList.Remove(e.DiedPlayerID);

            this.DisconnectEvents(sender as Player.Player);

            this.PlayerList.Remove(sender as Player.Player);
        }
        private void NewRelationship(object sender, NewRelationshipEventArgs e)
        {
            // for party a
            if (this.relationshipList[e.ID1].ContainsKey(e.ID2) == false)
                this.relationshipList[e.ID1].Add(e.ID2, DiplomaticRelationshipType.Peace);
            // for party b
            if (this.relationshipList[e.ID2].ContainsKey(e.ID1) == false)
                this.relationshipList[e.ID2].Add(e.ID1, DiplomaticRelationshipType.Peace);
        }
        private void DeclearWar(object sender, DeclearWarEventArgs e)
        {
            this.relationshipList[e.PartyA][e.PartyB] = DiplomaticRelationshipType.War;
            this.relationshipList[e.PartyB][e.PartyA] = DiplomaticRelationshipType.War;
        }
        private void MapObjectKilled(object sender, MapObjectKilledEventArgs e)
        {
            var players = from p in PlayerList
                         where p.ID == e.DiedObj.OwnerID
                         select p;
            Player.Player player;
            if (players.Count() > 0)
                player = players.First();
            else
                if (e.DiedObj.OwnerID == this.HumPlayer.ID)
                    player = this.HumPlayer;
                else
                    throw new Exception("Should Not reach");
            if (e.DiedObj is Unit.Unit)
                player.RemoveUnit(e.DiedObj as Unit.Unit);
            else
                throw new Exceptions.ShouldNotReachException("Currently only unit could be killed.");
            GameMap.UniqueInstance.Remove(e.DiedObj.ID); // current only unit
        }
        #endregion

        #region next turn
        /// <summary>
        /// return which player has finished its turn
        /// </summary>
        /// <returns></returns>
        public void NextTurn()
        {
            for (int i = 0; i < this.PlayerList.Count; i++)
            {
                this.PlayerList[i].NextTurn();                
            }
            this.IncreaseTurnNumber();
            this.HumPlayer.StartNextTurn();
        }
        #endregion

        #region get income list
        /// <summary>
        /// Return all known player's income of player with id
        /// </summary>
        public Dictionary<uint, float> IncomeOfEveryPlayer(uint id)
        {
            Dictionary<uint,float> dic = new Dictionary<uint,float>();
            foreach (var a in from p in this.GetAllPlayers()   // get all known player
                              where this.relationshipList[id].ContainsKey(p.ID) == true
                              select p)
            {
                dic.Add(a.ID, a.TotalIncome);
            }
            return dic;
        }

        private IEnumerable<Player.Player> GetAllPlayers()
        {
            HashSet<Player.Player> players = new HashSet<Player.Player>(this.PlayerList);
            if (this.HumPlayer != null)
                players.Add(HumPlayer);
            return players;
        }
        #endregion
    }
}
